#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/version.h>
#include <media/v4l2-device.h> /* v4l2_device_register */
#include <media/v4l2-ioctl.h> /* video_ioctl2 */
#include <media/v4l2-dev.h> /* video_register_device */
#include "v4l2_fops.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ChongYu");
MODULE_DESCRIPTION("quick v4l2 sample (demo)");

#ifdef BUILD_VERSION
#pragma message(BUILD_VERSION)
#endif

#define DEMO_DEV_NAME    "v4l2_dev"

struct demo_dev {
    struct device *dev;
    struct video_device vdev;
    struct v4l2_device v4l2_dev;
};

static const struct v4l2_file_operations demo_v4l2_fops = {
    .owner          = THIS_MODULE,
    .open           = demo_v4l2_open,
    .release        = demo_v4l2_release,
    .poll           = demo_v4l2_poll,
    .unlocked_ioctl = video_ioctl2, // ?
    .mmap           = demo_v4l2_mmap
};

static int demo_probe(struct platform_device *pdev)
{
    int ret;
    const char name[] = "v4l2-demo";
    struct demo_dev *demo;

    printk("v4l2 demo probe\n");
 
    demo = devm_kzalloc(&pdev->dev, sizeof(struct demo_dev), GFP_KERNEL);
    if (demo == NULL)
        return -EINVAL;

    platform_set_drvdata(pdev, demo); // pdev->dev->driver_data ---> demo
    demo->vdev.fops = &demo_v4l2_fops;
    demo->vdev.v4l2_dev = &demo->v4l2_dev;
    demo->vdev.release = video_device_release_empty;
    strncpy(demo->vdev.name, name, sizeof(name));

    ret = v4l2_device_register(&pdev->dev, &demo->v4l2_dev); // kref?
    if (ret != 0) {
        printk("v4l2 register error!\n");
        return ret;
    }

#if (KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE)
    demo->vdev.device_caps |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT; // 非subdev要置位
    ret = video_register_device(&demo->vdev, VFL_TYPE_VIDEO, -1);
#else
    ret = video_register_device(&demo->vdev, VFL_TYPE_GRABBER, -1);
#endif
    if (ret != 0) {
        printk("video register device error!\n");
        return ret;
    }

    return 0;
}

static int demo_remove(struct platform_device *pdev)
{
    struct demo_dev *demo;

    printk("v4l2 demo remove\n");
    demo = platform_get_drvdata(pdev);
    v4l2_device_unregister(&demo->v4l2_dev);
    return 0;
}

static struct platform_driver demo_driver = {
    .probe  = demo_probe,
    .remove = demo_remove,
    .driver = {
        .name   = DEMO_DEV_NAME,
        .owner  = THIS_MODULE,
    },
};

static int __init demo_init(void)
{
    int ret;

    printk("v4l2 demo init\n");
    ret = platform_driver_register(&demo_driver);
    if (ret != 0) {
        printk("platform register failed\n");
        return ret;
    }

    return 0;    
}

static void __exit demo_exit(void)
{
    printk("v4l2 demo exit\n");
    platform_driver_unregister(&demo_driver);
}

module_init(demo_init);
module_exit(demo_exit);
